package com.zkh.myutils.database.data;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import javax.sql.DataSource;

import com.zkh.myutils.database.DatabaseException;
import com.zkh.myutils.database.data.handler.Handler;
import com.zkh.myutils.database.dbpool.ConnectConfigure;
import com.zkh.myutils.io.log.Logger;
import com.zkh.myutils.system.ConfigLoader;
import com.zkh.myutils.utils.ArrayUtils;

/**
 * 数据库操作类，专门用于封装结果集数据。<br /><br />
 * 
 * 数据库资源使用优先级：<br />
 * 优先级1：参数连接对象Connection<br />
 * 优先级2：构造器数据源DataSource或构造器连接对象Connection <br /><b>
 * 注：通过构造器传入的连接对象会自动关闭，在query，update时传入的连接对象，不做关闭处理，需自行手动关闭。</b>
 * 
 * @author zkh
 */
public class QueryRunner {
	//数据源
	private DataSource dataSource;
	//数据连接
	private Connection conn;
	//是否打印批量SQL参数
	private static boolean printBatchSqlParam = "true".equals(ConfigLoader.getString("expand.printBatchSqlParam", true));
	
	/**
	 * 构造器
	 */
	public QueryRunner(){}
	/**
	 * 构造器
	 * @param ds 数据源
	 */
	public QueryRunner(DataSource ds){
		this.dataSource = ds;
	}
	
	/**
	 * 构造器
	 * @param conn 数据连接对象
	 */
	public QueryRunner(Connection conn){
		this.conn = conn;
	}
	
	/**
	 * 执行查询操作
	 * @param sql SQL语句
	 * @param handler 处理数据对象
	 * @param params 参数
	 */
	public <T> T query(String sql, Handler<T> handler, Object... params){
		return query(true, sql, handler, params);
	}
	
	/**
	 * 执行查询操作
	 * @param conn 数据库连接
	 * @param sql SQL语句
	 * @param handler 处理数据对象
	 * @param params 参数
	 */
	public <T> T query(Connection conn, String sql, Handler<T> handler, Object... params){
		if(conn==null){
			throw new DatabaseException("连接对象不能为空");
		}
		//保存连接
		this.conn = conn;
		//查询
		return query(false, sql, handler, params);
	}
	
	/**
	 * 执行查询操作
	 * @param closeConn 是否关闭连接
	 * @param sql SQL语句
	 * @param handler 处理数据对象
	 * @param params 参数
	 */
	private <T> T query(boolean closeConn, String sql, Handler<T> handler, Object... params){
		if(conn==null && dataSource==null){
			throw new DatabaseException("未传入数据库连接和数据源");
		}
		if(sql==null){
			throw new DatabaseException("SQL语句不能为空");
		}
		//初始化
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		//捕获异常
		try{
			//连接
			conn = this.conn!=null?this.conn:dataSource.getConnection();
			//PreparedStatement对象
			pstmt = conn.prepareStatement(sql);
			//设置参数
			setParam(pstmt, params);
			//打印日期
			printLog(sql, params, false);
			//记录时间
			long start = System.currentTimeMillis();
			//获取结果集
			rs = pstmt.executeQuery();
			//打印执行时间
			printExecuteTime(start, System.currentTimeMillis());
			//获取结果
			return handler.handle(rs);
		}catch(SQLException e){
			//抛出异常
			throw new DatabaseException(e);
		}finally{
			//关闭资源
			DBOperateUtils.close(rs);
			DBOperateUtils.close(pstmt);
			//关闭连接
			if(closeConn){
				DBOperateUtils.close(conn);
			}
		}
	}
	
	/**
	 * 普通增、删、改操作，返回受影响行数
	 * @param sql SQL语句
	 * @param params 参数
	 */
	public int update(String sql, Object... params){
		return (int) execute(false, true, sql, params);
	}
	
	/**
	 * 普通增、删、改操作，返回受影响行数
	 * @param conn 数据库连接
	 * @param sql SQL语句
	 * @param params 参数
	 */
	public int update(Connection conn, String sql, Object... params){
		if(conn==null){
			throw new DatabaseException("连接对象不能为空");
		}
		//保存连接
		this.conn = conn;
		//查询
		return (int) execute(false, false, sql, params);
	}
	
	/**
	 * 插入数据，返回自增主键
	 * @param sql SQL语句
	 * @param params 参数
	 * @return 自增主键
	 */
	public BigDecimal insert(String sql, Object... params) {
		return (BigDecimal) execute(true, true, sql, params);
	}
	
	/**
	 * 插入数据，返回自增主键
	 * @param conn 数据库连接
	 * @param sql SQL语句
	 * @param params 参数
	 */
	public BigDecimal insert(Connection conn, String sql, Object... params){
		if(conn==null){
			throw new DatabaseException("连接对象不能为空");
		}
		//保存连接
		this.conn = conn;
		//查询
		return (BigDecimal) execute(true, false, sql, params);
	}
	
	/**
	 * 执行插入、更新或删除操作
	 * @param rtnWithKey 是否返回自增主键
	 * @param closeConn 是否关闭连接对象
	 * @param sql SQL语句
	 * @param params 参数
	 * @return 返回受影响的行数
	 */
	private Object execute(boolean rtnWithKey, boolean closeConn, String sql, Object... params){
		if(conn==null && dataSource==null){
			throw new DatabaseException("未传入数据库连接和数据源");
		}
		if(sql==null){
			throw new DatabaseException("SQL语句不能为空");
		}
		//初始化
		Connection conn = null;PreparedStatement pstmt = null;
		//捕获异常
		try{
			//连接
			conn = this.conn!=null?this.conn:dataSource.getConnection();
			//PreparedStatement对象
			pstmt = rtnWithKey ? conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) : conn.prepareStatement(sql);
			//设置参数
			setParam(pstmt, params);
			//打印日期
			printLog(sql, params, false);
			//记录时间
			long start = System.currentTimeMillis();
			//执行
			int impact = pstmt.executeUpdate();
			//打印执行时间
			printExecuteTime(start, System.currentTimeMillis());
			//不返回自增主键
			if(!rtnWithKey) {
				return impact;
			}
			//获取生成的key
			ResultSet rs = pstmt.getGeneratedKeys();
			//获取值
			if(rs.next()) {
				return rs.getBigDecimal(1);
			}
			//没有自增主键，返回null
			return null;
		}catch(SQLException e){
			//抛出异常
			throw new DatabaseException(e);
		}finally{
			//关闭资源
			DBOperateUtils.close(pstmt);
			//关闭连接
			if(closeConn){
				DBOperateUtils.close(conn);
			}
		}
	}
	
	/**
	 * 批量添加或更新，返回每条语句受影响条数
	 * @param sql SQL语句
	 * @param params 参数
	 */
	public int[] batch(String sql, Object[][] params){
		return (int[]) batch(false, true, sql, params, false);
	}
	
	/**
	 * 批量添加或更新，返回每条语句受影响条数
	 * @param sql SQL语句
	 * @param params 参数
	 */
	public int[] batch(String sql, List<Object[]> params){
		return (int[]) batch(false, true, sql, params, false);
	}
	
	/**
	 * 批量添加，返回每条语句的自增ID，与参数顺序一致
	 * @param sql SQL语句
	 * @param params 参数
	 */
	public BigDecimal[] batchInsert(String sql, Object[][] params){
		return (BigDecimal[]) batch(true, true, sql, params, false);
	}
	
	/**
	 * 批量添加，返回每条语句的自增ID，与参数顺序一致
	 * @param sql SQL语句
	 * @param params 参数
	 */
	@SuppressWarnings("unchecked")
	public List<BigDecimal> batchInsert(String sql, List<Object[]> params){
		return (List<BigDecimal>) batch(true, true, sql, params, false);
	}
	
	/**
	 * 批量添加或更新，返回每条语句受影响条数
	 * @param conn 数据库连接
	 * @param sql SQL语句
	 * @param params 参数
	 */
	public int[] batch(Connection conn, String sql, Object[][] params){
		if(conn==null){
			throw new DatabaseException("连接对象不能为空");
		}
		//保存连接
		this.conn = conn;
		//执行
		return (int[]) batch(false, false, sql, params, false);
	}
	
	/**
	 * 批量添加或更新，返回每条语句受影响条数
	 * @param conn 数据库连接
	 * @param sql SQL语句
	 * @param params 参数
	 */
	public int[] batch(Connection conn, String sql, List<Object[]> params){
		if(conn==null){
			throw new DatabaseException("连接对象不能为空");
		}
		//保存连接
		this.conn = conn;
		//执行
		return (int[]) batch(false, false, sql, params, false);
	}
	
	/**
	 * 批量添加，返回每条语句的自增ID，与参数顺序一致
	 * @param conn 数据库连接
	 * @param sql SQL语句
	 * @param params 参数
	 */
	public BigDecimal[] batchInsert(Connection conn, String sql, Object[][] params){
		if(conn==null){
			throw new DatabaseException("连接对象不能为空");
		}
		//保存连接
		this.conn = conn;
		//执行
		return (BigDecimal[]) batch(true, false, sql, params, false);
	}
	
	/**
	 * 批量添加，返回每条语句的自增ID，与参数顺序一致
	 * @param conn 数据库连接
	 * @param sql SQL语句
	 * @param params 参数
	 */
	@SuppressWarnings("unchecked")
	public List<BigDecimal> batchInsert(Connection conn, String sql, List<Object[]> params){
		if(conn==null){
			throw new DatabaseException("连接对象不能为空");
		}
		//保存连接
		this.conn = conn;
		//执行
		return (List<BigDecimal>) batch(true, false, sql, params, false);
	}
	
	/**
	 * 批量操作。Connection为事务连接对象，本方法的批量操作不会提交，由调用该方法的方法体提交并关闭连接。
	 * @param conn 数据库连接。调用本方法前请关闭自动提交；调用本方法后请手动提交，并恢复自动提交（如有必要），然后关闭连接
	 * @param sql SQL语句
	 * @param params 参数
	 * @return 返回每条语句受影响条数
	 */
	public int[] batchTransaction(Connection conn, String sql, Object[][] params) {
		if(conn==null){
			throw new DatabaseException("连接对象不能为空");
		}
		//保存连接
		this.conn = conn;
		//执行
		return (int[]) batch(false, false, sql, params, true);
	}
	
	/**
	 * 批量操作。Connection为事务连接对象，本方法的批量操作不会提交，由调用该方法的方法体提交并关闭连接。
	 * @param conn 数据库连接。调用本方法前请关闭自动提交；调用本方法后请手动提交，并恢复自动提交（如有必要），然后关闭连接
	 * @param sql SQL语句
	 * @param params 参数
	 * @return 返回每条语句受影响条数
	 */
	public int[] batchTransaction(Connection conn, String sql, List<Object[]> params) {
		if(conn==null){
			throw new DatabaseException("连接对象不能为空");
		}
		//保存连接
		this.conn = conn;
		//执行
		return (int[]) batch(false, false, sql, params, true);
	}
	
	/**
	 * 批量操作。Connection为事务连接对象，本方法的批量操作不会提交，由调用该方法的方法体提交并关闭连接。
	 * @param conn 数据库连接。调用本方法前请关闭自动提交；调用本方法后请手动提交，并恢复自动提交（如有必要），然后关闭连接
	 * @param sql SQL语句
	 * @param params 参数
	 * @return 返回每条语句的自增ID，与参数顺序一致
	 */
	public BigDecimal[] batchInsertTransaction(Connection conn, String sql, Object[][] params) {
		if(conn==null){
			throw new DatabaseException("连接对象不能为空");
		}
		//保存连接
		this.conn = conn;
		//执行
		return (BigDecimal[]) batch(true, false, sql, params, true);
	}
	
	/**
	 * 批量操作。Connection为事务连接对象，本方法的批量操作不会提交，由调用该方法的方法体提交并关闭连接。
	 * @param conn 数据库连接。调用本方法前请关闭自动提交；调用本方法后请手动提交，并恢复自动提交（如有必要），然后关闭连接
	 * @param sql SQL语句
	 * @param params 参数
	 * @return 返回每条语句的自增ID，与参数顺序一致
	 */
	@SuppressWarnings("unchecked")
	public List<BigDecimal> batchInsertTransaction(Connection conn, String sql, List<Object[]> params) {
		if(conn==null){
			throw new DatabaseException("连接对象不能为空");
		}
		//保存连接
		this.conn = conn;
		//执行
		return (List<BigDecimal>) batch(true, false, sql, params, true);
	}
	
	/**
	 * 批量添加或更新(执行完批量更新后，会将Connection的自动提交设置为true)
	 * @param rtnWithKey 是否返回自增主键
	 * @param closeConn 是否关闭连接
	 * @param sql SQL语句
	 * @param params 参数
	 * @param transaction 是否为事务批量操作，若为true，则本方法不会提交，且不会关闭连接，此时参数closeConn无效。
	 */
	private Object batch(boolean rtnWithKey, boolean closeConn, String sql, Object params, boolean transaction){
		if(conn==null && dataSource==null){
			throw new DatabaseException("不存在有效的数据库连接或数据源");
		}
		if(sql==null){
			throw new DatabaseException("SQL语句不能为空");
		}
		//初始化
		Connection conn = null;PreparedStatement pstmt = null;
		//捕获异常
		try{
			//连接
			conn = this.conn!=null?this.conn:dataSource.getConnection();
			//事务操作由上级方法关闭自动提交
			if(!transaction) {
				//关闭自动提交
				conn.setAutoCommit(false);
			}
			//PreparedStatement对象
			pstmt = rtnWithKey ? conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) : conn.prepareStatement(sql);
			//设置参数。返回-1，表示返回集合，大于-1，表示返回数组
			int len = setBatchParam(pstmt, params);
			//打印日志
			printLog(sql, params, true);
			//记录时间
			long start = System.currentTimeMillis();
			//执行
			int[] res = pstmt.executeBatch();
			//执行时长
			printExecuteTime(start, System.currentTimeMillis());
			//事务操作由上级提交并开启自动提交
			if(!transaction) {
				//提交
				conn.commit();
				//提交自动提交
				conn.setAutoCommit(true);
			}
			//不生成自增主键
			if(!rtnWithKey) {
				return res;
			}
			//获取自增主键
			ResultSet rs = pstmt.getGeneratedKeys();
			//返回集合标识
			boolean rtnList = len==-1;
			//初始化结果集
			List<BigDecimal> listres = new ArrayList<>();
			BigDecimal[] arrres = new BigDecimal[rtnList ? 0 : len];
			//数组计数
			int count = 0;
			//开始遍历
			while(rs.next()) {
				//获取值
				BigDecimal v = rs.getBigDecimal(1);
				//赋值
				if(rtnList) {
					listres.add(v);
				}else {
					arrres[count++] = v;
				}
			}
			//返回
			return rtnList ? listres : arrres;
		}catch(SQLException e){
			//如果没有提交事务，则回滚，且开启自动提交
			try {
				if(!transaction) {
					//回滚
					conn.rollback();
					//设置自动提交
					conn.setAutoCommit(true);
				}
			}catch(SQLException ee) {}
			//抛出异常
			throw new DatabaseException(e);
		}finally{
			//关闭资源
			DBOperateUtils.close(pstmt);
			//关闭连接
			if(closeConn){
				DBOperateUtils.close(conn);
			}
		}
	}
	
	/**
	 * 设置值
	 * @param pstmt 预处理对象
	 * @param params 参数
	 */
	private void setParam(PreparedStatement pstmt, Object[] params) throws SQLException {
		//设置参数
		if(params!=null && params.length>0){
			for(int i=0,len=params.length;i<len;i++){
				//设置值
				pstmt.setObject(i+1, params[i]);
			}
		}
	}
	
	/**
	 * 设置值（批量）
	 * @param pstmt 预处理对象
	 * @param parameter 参数
	 */
	@SuppressWarnings("unchecked")
	private int setBatchParam(PreparedStatement pstmt, Object parameter) throws SQLException {
		//没有参数
		if(parameter==null) {
			throw new DatabaseException("批处理参数为null");
		}
		//设置值
		Consumer<Object[]> consumer = param->{
			try {
				//遍历每组参数
				for(int j=0,plen=param.length;j<plen;j++){
					//设置值
					pstmt.setObject(j+1, param[j]);
				}
				pstmt.addBatch();
			}catch(SQLException e) {
				throw new DatabaseException(e);
			}
		};
		//集合参数
		if(parameter instanceof List) {
			((List<Object[]>) parameter).forEach(consumer);
			return -1;//表示集合
		}else {//数组参数
			Object[][] params = (Object[][]) parameter;
			//遍历赋值
			ArrayUtils.forEach(params, (e, i)->consumer.accept(e));
			//返回长度
			return params.length;
		}
	}
	
	/**
	 * 打印SQL及参数到控制台
	 * @param sql sql语句
	 * @param params 参数
	 */
	private void printLog(String sql, Object params, boolean batch) {
		//是否显示SQL
		if(ConnectConfigure.isShowSql()) {
			//打印日志
			Logger.info("执行语句：{}", sql);
			//批处理
			if(params!=null && batch) {
				//集合
				boolean isList = params instanceof List;
				//数据
				List<?> listDatas = isList ? (List<?>) params : null;
				Object[][] arrayDatas = !isList ? (Object[][]) params : null;
				//长度
				int len = isList ? listDatas.size() : arrayDatas.length;
				//打印参数
				if(printBatchSqlParam) {
					//遍历
					for(int i=0; i<len; i++) {
						//当前
						Object[] curr = isList ? (Object[]) listDatas.get(i) : arrayDatas[i];
						//输出日志
						Logger.info("执行参数{}：{}", i+1, Arrays.toString(curr));
					}
				}else {
					Logger.printLog("执行参数：共提交参数{}条", len);
				}
			}else {
				//普通参数
				Logger.info("执行参数：{}", params==null ? "[]" : Arrays.toString((Object[]) params));
			}
		}
	}
	
	/**
	 * 打印执行时长
	 * @param start 开始执行时间戳
	 * @param end 执行结束时间戳
	 */
	private void printExecuteTime(long start, long end) {
		//是否显示SQL
		if(ConnectConfigure.isShowSql()) {
			Logger.info("执行时间：{}ms", end-start);
		}
	}
}
